// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "table.h"
#include "addrspace.h"
#include "ProcessTable.h"
#include <stdio.h>
#include <iostream>

extern "C" { int bzero(char *, int); };

using namespace std;

int copyin(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes from the current thread's virtual address vaddr.
    // Return the number of bytes so read, or -1 if an error occors.
    // Errors can generally mean a bad virtual address was passed in.
    bool result;
    int n=0;			// The number of bytes copied in
    int *paddr = new int;

    while ( n >= 0 && n < len) {
      result = machine->ReadMem( vaddr, 1, paddr );
      while(!result) // FALL 09 CHANGES
	  {
   			result = machine->ReadMem( vaddr, 1, paddr ); // FALL 09 CHANGES: TO HANDLE PAGE FAULT IN THE ReadMem SYS CALL
	  }	
      
      buf[n++] = *paddr;
     
      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    delete paddr;
    return len;
}

int copyout(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes to the current thread's virtual address vaddr.
    // Return the number of bytes so written, or -1 if an error
    // occors.  Errors can generally mean a bad virtual address was
    // passed in.
    bool result;
    int n=0;			// The number of bytes copied in

    while ( n >= 0 && n < len) {
      // Note that we check every byte's address
      result = machine->WriteMem( vaddr, 1, (int)(buf[n++]) );

      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    return n;
}

void Create_Syscall(unsigned int vaddr, int len) {
    // Create the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  No
    // way to return errors, though...
    char *buf = new char[len+1];	// Kernel buffer to put the name in

    if (!buf) return;

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Create\n");
	delete buf;
	return;
    }

    buf[len]='\0';

    fileSystem->Create(buf,0);
    delete[] buf;
    return;
}

int Open_Syscall(unsigned int vaddr, int len) {
    // Open the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  If
    // the file is opened successfully, it is put in the address
    // space's file table and an id returned that can find the file
    // later.  If there are any errors, -1 is returned.
    char *buf = new char[len+1];	// Kernel buffer to put the name in
    OpenFile *f;			// The new open file
    int id;				// The openfile id

    if (!buf) {
	printf("%s","Can't allocate kernel buffer in Open\n");
	return -1;
    }

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Open\n");
	delete[] buf;
	return -1;
    }

    buf[len]='\0';

    f = fileSystem->Open(buf);
    delete[] buf;

    if ( f ) {
	if ((id = currentThread->space->fileTable.Put(f)) == -1 )
	    delete f;
	return id;
    }
    else
	return -1;
}

void Write_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one. For disk files, the file is looked
    // up in the current address space's open file table and used as
    // the target of the write.
    
    char *buf;		// Kernel buffer for output
    OpenFile *f;	// Open file for output

    if ( id == ConsoleInput) return;
    
    if ( !(buf = new char[len]) ) {
		printf("%s","Error allocating kernel buffer for write!\n");
		return;
	} else {
		if ( copyin(vaddr,len,buf) == -1 ) {
			printf("%s","Bad pointer passed to to write: data not written\n");
			delete[] buf;
			return;
		}
    }

    if ( id == ConsoleOutput) {
      for (int ii=0; ii<len; ii++) {
	printf("%c",buf[ii]);
      }

    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    f->Write(buf, len);
	} else {
	    printf("%s","Bad OpenFileId passed to Write\n");
	    len = -1;
	}
    }

    delete[] buf;
}

int Read_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one.    We reuse len as the number of bytes
    // read, which is an unnessecary savings of space.
    char *buf;		// Kernel buffer for input
    OpenFile *f;	// Open file for output

    if ( id == ConsoleOutput) return -1;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer in Read\n");
	return -1;
    }

    if ( id == ConsoleInput) {
      //Reading from the keyboard
      scanf("%s", buf);

      if ( copyout(vaddr, len, buf) == -1 ) {
	printf("%s","Bad pointer passed to Read: data not copied\n");
      }
    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    len = f->Read(buf, len);
	    if ( len > 0 ) {
	        //Read something from the file. Put into user's address space
  	        if ( copyout(vaddr, len, buf) == -1 ) {
		    printf("%s","Bad pointer passed to Read: data not copied\n");
		}
	    }
	} else {
	    printf("%s","Bad OpenFileId passed to Read\n");
	    len = -1;
	}
    }

    delete[] buf;
    return len;
}

void Close_Syscall(int fd) {
    // Close the file associated with id fd.  No error reporting.
    OpenFile *f = (OpenFile *) currentThread->space->fileTable.Remove(fd);

    if ( f ) {
      delete f;
    } else {
      printf("%s","Tried to close an unopen file\n");
    }
}


void PageCallExceptionHandler(int vaddr){
	// A. Get the virtual page required. 
	// convert virtual page address to virtual page number
	unsigned int vpn = vaddr / PageSize;
	// 1. Ensure that the page is within a legal range.
	
	// B. Update the timestamp in the IPT for each valid entry of the TLB.
	for (int i = 0; i < TLBSize; i++) {
		if (machine->tlb[i].valid) {
			PageTableEntry* pageTableEntry = ((PageTableEntry*)(invertedPageTable->Get(i)));
			pageTableEntry->timestamp = machine->getTimeUsed(i); // TODO: Fix this
		}
	}
	// C. Check to see whether the needed page is in the IPT.
	PageTableEntry*	ptEntry  = currentThread->space->getPageTableEntry(vpn);
	PageTableEntry* iptEntry;
		//PageTableEntry* iptEntry = ((PageTableEntry*)(invertedPageTable->Get(ptEntry.physicalPage)));
	if (ptEntry->pageLocation != PAGE_MEMORY) { // Page not found in IPT, need to load it from disk.
		// 1. If so, go on to step D.
		// 2. If not, then the page is either in the executable or the swap file and needs to be loaded into memory. 
		  // a. This in turn means that there must be an available slot in main memory.
		// 3. Select a page in main memory in which to place the new page.
		int selectedPage = -1;
		for (int i = 0; i < NumPhysPages; i++) {
			iptEntry = ((PageTableEntry*) (invertedPageTable->Get(i)));
			ASSERT(iptEntry->physicalPage == i);
			if (iptEntry->valid == false) {
				// found a page in the IPT that is not currently in use.
				selectedPage = i;
				break;
			}
		}
			// a. If there is an unused page, use it.
		if (selectedPage == -1) { // No open physical page, will need to evict something
			// b. If there are no unused pages and random page replacement is being used, select a random page.
				//selectedPage = rand() * NumPhysPages;
				selectedPage = 4;
			// c. If there are no unused pages and Nachos LRU page replacement is being used, select the page in the IPT with the lowest timestamp.
			//selectedPage = least recently used;
		
			// 4. Check to see whether the selected page is in the TLB.
			for (int i = 0; i < TLBSize; i++) {
				if (machine->tlb[i].valid && machine->tlb[i].physicalPage == selectedPage) {
					// Page selected for eviction is currently in the TLB
					// a. If so, set it invalid and propagate the dirty bit back to the IPT. 
					machine->tlb[i].valid = FALSE;
					if (machine->tlb[i].dirty == TRUE) {
						((PageTableEntry*) invertedPageTable->Get(machine->tlb[i].physicalPage))->dirty = 
							machine->tlb[i].dirty;
					}
				}
			}
			
			
			PageTableEntry* evictedIPTEntry = ((PageTableEntry*) invertedPageTable->Get(selectedPage));
			
			KernelProcessEntry* processEntry = ProcessTable::getInstance()->getProcess(evictedIPTEntry->processId);
			//KernelThreadEntry* threadEntry = processEntry->threadTable->Get(0);
			
			PageTableEntry* evictedPTEntry = processEntry->currentSpace->getPageTableEntry(evictedIPTEntry->virtualPage);
			
			if (evictedIPTEntry->dirty == TRUE) {			
				// 5. Swap the page from main memory to the swap file. 
				
					// a. Depending on which implementation of the swap file you use, the page may or may not already have a swap file page associated with it. 
					int swapFileIndex = -1;
					swapMapLock->Acquire();
					swapFileIndex = swapBitMap->Find();
					swapMapLock->Release();
					
					if (swapFileIndex == -1) {
						DEBUG('a', "PageFaultException: Could not find free page in the swap file.");
						interrupt->Halt();
					}
					// b. Write the contents of the page into the swap file from main memory.
					swapFile->WriteAt(&(machine->mainMemory[selectedPage*PageSize]),
				                    PageSize, swapFileIndex*PageSize);
					// c. Update the page table to reflect the location of the evicted page.
				evictedPTEntry->physicalPage = swapFileIndex; // Store the page index 
				evictedPTEntry->pageLocation = PAGE_SWAPFILE;
				evictedIPTEntry->valid = FALSE;
				
			}
		}
	
	// Open physical page was found.

		// 6. Now that there is an available slot in main memory, copy the needed page into it. 
		  // a. Depending on where the page is stored (executable or swap file), read it from there into main memory.
		if (ptEntry->pageLocation == PAGE_SWAPFILE) {
			// Read in from Memory
			printf("PageFaultHandler: Reading from Swapfile index = %d.\n", ptEntry->physicalPage);
			swapFile->ReadAt(&(machine->mainMemory[selectedPage*PageSize]), PageSize,
				currentThread->space->codeInFileAddr + (ptEntry->physicalPage)*PageSize);
			swapMapLock->Acquire();
			swapBitMap->Clear(ptEntry->physicalPage);
			swapMapLock->Release();
		}
		else if (ptEntry->pageLocation == PAGE_EXECUTABLE) {
			// Read in from Executable
			printf("PageFaultHandler: PAGE_LOC_UNKNOWN case.\n");
			currentThread->space->readFromExecutable(selectedPage, vpn);
		}
		else if (ptEntry->pageLocation == PAGE_LOC_UNKNOWN) {
			// Location unknown, likely the first time this page was loaded to memory.
			printf("PageFaultHandler: PAGE_LOC_UNKNOWN case.\n");
			bzero(&(machine->mainMemory[selectedPage*PageSize]), PageSize);
		}
		else {
			DEBUG('a', "Unspecified location for page %d on pagefault.", vpn);
			interrupt->Halt();
		}
		
		// Update the page table in the addrspace with the new location information
		ptEntry->pageLocation = PAGE_MEMORY;
		ptEntry->physicalPage = selectedPage;
		
		// 7. Update the IPT entry to correspond to the new page (PID, virtual page number, physical page number).
		iptEntry->valid = TRUE;
		iptEntry->dirty = ptEntry->dirty;
		iptEntry->readOnly = ptEntry->readOnly;
		iptEntry->processId = currentThread->space->spaceId;
		iptEntry->pageLocation = PAGE_MEMORY;
		iptEntry->virtualPage = ptEntry->virtualPage;
		
	}
	
	// at this point the requested virtual page is in main memory at page selectedPage.
	printf("ASSERT: IPT.vpn:%d == vpn:%d.\n", ((PageTableEntry*) invertedPageTable->Get(ptEntry->physicalPage))->virtualPage, vpn);
	ASSERT(((PageTableEntry*) invertedPageTable->Get(ptEntry->physicalPage))->virtualPage == vpn);
	
	// D. Update the TLB with the needed page. 
	// 1. If the entry in the TLB that is to be replaced is valid and dirty, propagate the dirty bit back to the IPT. 
	// check if current value in the tlb is dirty
	if (machine->tlb[tlbCounter].valid == TRUE && machine->tlb[tlbCounter].dirty == TRUE){
		printf("TLB entry %d is dirty on PageFaultEviction. Should be written back.\n", tlbCounter);
		// propagate the dirty bit to the IPT
		((PageTableEntry*) invertedPageTable->Get(machine->tlb[tlbCounter].physicalPage))->dirty = 
			machine->tlb[tlbCounter].dirty;
	}
   // 2. Copy the information about the needed page into the TLB (virtual page, physical page).
   // read new address into tlb
	PageTableEntry* pageEntry = currentThread->space->getPageTableEntry(vpn);
	machine->tlb[tlbCounter].virtualPage 	= pageEntry->virtualPage;
	machine->tlb[tlbCounter].physicalPage 	= pageEntry->physicalPage;
	machine->tlb[tlbCounter].valid 			= pageEntry->valid;
	machine->tlb[tlbCounter].dirty 			= pageEntry->dirty;
	machine->tlb[tlbCounter].readOnly 		= pageEntry->readOnly;
	machine->tlb[tlbCounter].use 			= pageEntry->use;
	//machine->tlb[tlbCounter] = pageEntry;
   // 3. Set the TLB entry's valid bit to true.
   machine->tlb[tlbCounter].valid  = TRUE;
// E. Increment the TLB counter used to select an entry to replace.
	// increment the counter
	tlbCounter++;
	if (tlbCounter == TLBSize){
		tlbCounter = 0;
	}
}


void exec_thread(int ID){
	//Intialize registers to run main and restore the page table.
	currentThread->space->InitRegisters();
	currentThread->space->RestoreState();
	DEBUG('a', "Exec Thread: initializing registers and pageTable pointer.");
	machine->Run();
}

/* Run the executable, stored in the Nachos file "name", and return the 
 * address space identifier
 */
SpaceId Exec_Syscall(int vaddr, int len) {
	DEBUG('b', "%s: Exec syscall.\n", currentThread->getName());
	// Open the exe from file system
	OpenFile *executable;
    AddrSpace *space;
	Thread* t;
	char* buf;
    
    if ( !(buf = new char[len]) ) {
		printf("%s","Error allocating kernel buffer for write!\n");
		return -1;
	} else {
		if ( copyin(vaddr,len,buf) == -1 ) {
			printf("%s","Bad pointer passed to to write: data not written\n");
			delete[] buf;
			return -1;
		}
    }
	
	executable = fileSystem->Open(buf);
	
    if (executable == NULL) {
		printf("Unable to open file %s.\n", buf);
		interrupt->Halt();
		return -1;
    }
	
	// Create a new address space
	space = new AddrSpace(executable);
	//Update the process table for a new process
    space->spaceId = ProcessTable::getInstance()->addNewProcess(space);
	
	// store the new processEntry
    KernelProcessEntry* newProcEntry = ProcessTable::getInstance()->getProcess(space->spaceId);
	newProcEntry->dataLock->Acquire();
	// set the space pointer
	newProcEntry->parentSpace = currentThread->space;
	newProcEntry->dataLock->Release();
	
	// store the current processEntry
	KernelProcessEntry* currentProcEntry = ProcessTable::getInstance()->getProcess(currentThread->space->spaceId);
	currentProcEntry->dataLock->Acquire();
	// increase the number of child processes
	++(currentProcEntry->numChildProcesses);
	currentProcEntry->dataLock->Release();


	t = new Thread("ExecThread");
	t->space = space;
	// give the thread an ID
	t->threadId = ProcessTable::getInstance()->addNewThread(space->spaceId);
    
    //delete executable;			// close file - disabled for demand paged memory

	//Write the ID of the space in register 2 to be used later
	//machine->WriteRegister(2, execSpaceID);
	
	// Fork a new thread for the main thread
	t->Fork(exec_thread,space->spaceId);
	
	return space->spaceId;
}


/* This user program is done (status = 0 means exited normally). */
Lock exitLock("The Exit Lock");
void Exit_Syscall(int status) {
	DEBUG('b', "%s: Exiting. Status = %d.\n", currentThread->getName(), status);
	printf("Exiting. Status = %d.\n", status);
	
	// store the thread and process entries
	KernelThreadEntry*  threadEntry 	= ProcessTable::getInstance()->getThread(currentThread->space->spaceId, currentThread->threadId);
    KernelProcessEntry* processEntry 	= ProcessTable::getInstance()->getProcess(currentThread->space->spaceId);
	
    threadEntry->ExitLock->Acquire();
    //waits until all children threads are dead
	DEBUG('b',"Exit: Waiting for child threads to exit.\n");
    while(threadEntry->numChildThreads > 0){
		threadEntry->ExitCV->Wait(threadEntry->ExitLock);
    }
    threadEntry->ExitLock->Release();
	
    // thread is clear to exit
    exitLock.Acquire();
    //wake up parent thread for exiting
	DEBUG('b',"Exit: Alerting parent thread I am exiting.\n");
    if(threadEntry->parentThread != NULL){
		// store the parent thread entry
		KernelThreadEntry* parentThreadEntry = ProcessTable::getInstance()->getThread(currentThread->space->spaceId, threadEntry->parentThread->threadId);                  
		parentThreadEntry->ExitLock->Acquire();
		// decrease the count of child threads
		--(parentThreadEntry->numChildThreads);
		// signal the parent if they are waiting for us to exit
		parentThreadEntry->ExitCV->Signal(parentThreadEntry->ExitLock);
		parentThreadEntry->ExitLock->Release();
    }
	// remove the current thread's threadEntry
    ProcessTable::getInstance()->removeThread(currentThread->space->spaceId, currentThread->threadId);
	
    /*processEntry->dataLock->Acquire();
    //if no more threads in address space
	// decrement the number of threads for this process and compare
	(processEntry->numThreads)--;
	processEntry->dataLock->Release();*/
	
	ASSERT(processEntry->numThreads >= 0);
	
	DEBUG('b', "Threads in currentProcess: %d. processID = %d.\n", processEntry->numThreads, currentThread->space->spaceId);
    if(processEntry->numThreads == 0) {
		// we are the last thread in this process, should close this process
		
		// wait for children processes to exit
		while(processEntry->numChildProcesses > 0){
			// wait here for our child processes to finish
			processEntry->ExitCV->Wait(&exitLock);
		}
		
		//if there is a parent, alert the parent
		if(processEntry->parentSpace != NULL){
			// store parent processEntry
			KernelProcessEntry* parentProcessEntry = ProcessTable::getInstance()->getProcess(processEntry->parentSpace->spaceId);
			parentProcessEntry->dataLock->Acquire();
			// decrement the number of child processes for the parent process
			--(parentProcessEntry->numChildProcesses);
			// Wake up our parent so they could possibly exit.
			parentProcessEntry->ExitCV->Signal(&exitLock);
			parentProcessEntry->dataLock->Release();
		}
	
		// remove our entry from the process table
		ProcessTable::getInstance()->removeProcess(currentThread->space->spaceId);
		
		// Final exiting cases
		// If we are the last process halt the system
		if(ProcessTable::getInstance()->getNumProcesses() == 0) {
			//last process
			DEBUG('b',"Exit:last thread exiting\n");
			exitLock.Release();
			delete currentThread->space;
			ProcessTable::deleteProcessTable();
			interrupt->Halt();
		}
		else {
			DEBUG('b',"Exit:process %d ending.\n", currentThread->space->spaceId);
			exitLock.Release();
			delete currentThread->space;  
			currentThread->Finish();        
		}
    } 
    else {
		// not last thread or last process
		
        //just finish
        DEBUG('b',"EXIT:thread %d in process %d is ending.\n", currentThread->threadId, currentThread->space->spaceId);
        exitLock.Release();
        currentThread->Finish();
    }
	
	
	/*//if (last executing thread in the last process){
	if (processTable->NumFilled() == 1 &&
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads == 1) {
		interrupt->Halt();
	}
	
	DEBUG('b', "StackReg = %x.\n", machine->ReadRegister(StackReg));
	
	//if (not the last executing thread in the process, nor is it the 
		// last process){
	if (processTable->NumFilled() != 1 &&
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads != 1) {
		// -release the stack memory for this thread
		currentThread->space->ClearStack();
		// -only the pages where the valid bit is true
		// decrement thread count
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads--;
		
		currentThread->Finish();
	}
	
	//if (the last executing thread in a process, but not the last process)
	if (processTable->NumFilled() != 1 &&
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads == 1) {
		//-clear out all Locks/CVs for that process
		for (int i = 0; i < MAX_LOCKS; i++) {
			SysLock* sysLock = (SysLock*) lockTable->Get(i);
			if (sysLock && sysLock->processID == currentThread->space)
				lockTable->Remove(i);
		}
		for (int i = 0; i < MAX_CONDITIONS; i++) {
			SysCondition* sysCondition = (SysCondition*) conditionTable->Get(i);
			if (sysCondition && sysCondition->processID == currentThread->space)
				conditionTable->Remove(i);
		}
		//-clear out all valid entries in the page table
		processTable->Remove(currentThread->space->processID);
		delete currentThread->space;
		
		currentThread->Finish();
	}

	//temporary until Exit_Syscall finished
	//currentThread->Finish();*/
}

void kernel_thread(int vaddr){
	//Zero out registers 
	int i;

    for (i = 0; i < NumTotalRegs; i++)
	machine->WriteRegister(i, 0);

	//Set PC to function to fork
    machine->WriteRegister(PCReg, vaddr);	

    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, vaddr+4);
	DEBUG('a', "FORK:SET REGISTERS TO BLANK AND UPDATE PC REG AND NEXT PC REG");
	//Add Stack and set stackreg and set pagetable
	currentThread->space->addStack();
	currentThread->space->RestoreState();
	DEBUG('a', "FORK:CREATE A STACK AND RESTORE PAGE TABLE TO MACHINE");
	machine->Run();
}

/* Fork a thread to run a procedure ("func") in the *same* address space 
 * as the current thread.
 */
void Fork_Syscall(unsigned int vaddr) {
	if (vaddr > (PageSize*(currentThread->space->getNumPages()+1))){
		printf("%s", "Fork: Error: given vaddr is not in the current address space.\n");
		interrupt->Halt();
	}
	else if (vaddr < 0){

		printf("%s", "Fork: Error: invalid vaddr given.\n");
		interrupt->Halt();
	}
	/*else if (vaddr > currentThread->space->codeAddr){
		printf("%s", "FORK:ERROR. INVALID FUNCTION ADDRESS. FUNCTION ADDRESS DOES NOT POINT TO CODE.\n");
		interrupt->Halt();
	}*/
	
	DEBUG('b', "%s: Fork_Syscall invoked. vaddr = %d.\n", currentThread->getName(), vaddr);
	//Must create a kernel thread
	Thread *t = new Thread("KernelThread");
	// Link new thread to the current process address space.
	t->space = currentThread->space;
	
	//update the process table, add this thread to this process's list of threads	
	KernelProcessEntry* processEntry = ProcessTable::getInstance()->getProcess(currentThread->space->spaceId);
	// set the threads ID
	t->threadId = ProcessTable::getInstance()->addNewThread(currentThread->space->spaceId); // set the threads ID
	KernelThreadEntry* newThreadEntry = ProcessTable::getInstance()->getThread(t->space->spaceId, t->threadId);
	// set the parentThread pointer
	newThreadEntry->parentThread = currentThread; 
	
	// store the parent (current) thread entry
	KernelThreadEntry* currentThreadEntry = ProcessTable::getInstance()->getThread(currentThread->space->spaceId, currentThread->threadId);
	currentThreadEntry->ExitLock->Acquire();
	// increment the number of child threads for the current thread
	(currentThreadEntry->numChildThreads)++; 
	currentThreadEntry->ExitLock->Release();
	
	/*// store the current process entry
	KernelProcessEntry* currentProcEntry = ProcessTable::getInstance()->getProcess(currentThread->space->spaceId); 
	currentProcEntry->dataLock->Acquire();
	// increment the number of active threads on it
	(currentProcEntry->numThreads)++; 
	currentProcEntry->dataLock->Release();*/
	
	//To have the thread execute code
	t->Fork(kernel_thread, vaddr);
}

/* Yield the CPU to another runnable thread, whether in this address space 
 * or not. 
 */
void Yield_Syscall() {
	currentThread->Yield();
}

void Acquire_Syscall(int index) {
	if (lockTable->Get(index) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",index);
		return;
	}
	
	DEBUG('b',"%s: Acquiring lock %d.\n", currentThread->getName(),index);
	((SysLock*) lockTable->Get(index))->lock->Acquire();
}

void Release_Syscall(int index) {
	if (lockTable->Get(index) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",index);
		return;
	}
	// Call release on the lock
	DEBUG('b',"%s: Releasing lock %d.\n", currentThread->getName(),index);
	((SysLock*) lockTable->Get(index))->lock->Release();
	// if the lock is "flagged to be deleted"
	if (((SysLock*) lockTable->Get(index))->flaggedForDelete){
		//if(lock is not in use){
			// If so delete the lock
			//lockTable->Remove(index);
		//}
	}
}

void Wait_Syscall(int conditionIndex, int lockIndex) {
	DEBUG('b',"Wait_Syscall invoked. condIndex=%d. lockIndex=%d.\n",conditionIndex,lockIndex);
	if (lockTable->Get(lockIndex) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",lockIndex);
		return;
	}
	if (conditionTable->Get(conditionIndex) == 0){
		DEBUG('a',"ERROR: Condition at index %d is equal to null.\n",conditionIndex);
		return;
	}
	// Call wait on the CV
	((SysCondition*) conditionTable->Get(conditionIndex))->condition->Wait(((SysLock*) lockTable->Get(lockIndex))->lock);
}

void Signal_Syscall(int conditionIndex, int lockIndex) {
	DEBUG('b',"Signal_Syscall invoked. condIndex=%d. lockIndex=%d.\n",conditionIndex,lockIndex);
	if (lockTable->Get(lockIndex) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",lockIndex);
		return;
	}
	if (conditionTable->Get(conditionIndex) == 0){
		DEBUG('a',"ERROR: Condition at index %d is equal to null.\n",conditionIndex);
		return;
	}
	// Signal the CV
	((SysCondition*) conditionTable->Get(conditionIndex))->condition->Signal(((SysLock*) lockTable->Get(lockIndex))->lock);
	// If the CV is "flagged to be deleted"
	if (((SysCondition*) conditionTable->Get(conditionIndex))->flaggedForDelete){
		// if(we are the last thread in it){
		//conditionTable->Remove(index);
		//}
	}
}

void Broadcast_Syscall(int conditionIndex, int lockIndex) {
	DEBUG('b',"Broadcast_Syscall invoked. condIndex=%d. lockIndex=%d.\n",conditionIndex,lockIndex);
	if (lockTable->Get(lockIndex) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",lockIndex);
		return;
	}
	if (conditionTable->Get(conditionIndex) == 0){
		DEBUG('a',"ERROR: Condition at index %d is equal to null.\n",conditionIndex);
		return;
	}
	// Broadcast to the CV
	((SysCondition*) conditionTable->Get(conditionIndex))->condition->Broadcast(((SysLock*) lockTable->Get(lockIndex))->lock);
	// If the CV is "flagged to be deleted"
	if (((SysCondition*) conditionTable->Get(conditionIndex))->flaggedForDelete){
		// By broadcasting to it we know the CV will be empty
		// delete the CV
		conditionTable->Remove(conditionIndex);
	}	
}

int CreateLock_Syscall(int name, int length) {
	// Create a new lock object
	SysLock* newLock = new SysLock(new Lock("Lock"), currentThread->space);
	// Add it to the lock array
	int index = lockTable->Put(newLock);
	DEBUG('b',"Creating Lock, at index %d.\n", index);
	return index;
}

void DestroyLock_Syscall(int index) {
	// if the lock is in use
		// set "to be destroyed flag" on the lock
		// ((SysLock) lockTable->Get(index))->flaggedForDelete = true;
	// else
		// Delete this lock and null the pointer in the array
	DEBUG('b',"Destroying Lock, at index %d.\n", index);
	lockTable->Remove(index);
	
}

int CreateCondition_Syscall(int name, int length) {
	// Create a new CV object
	SysCondition* newCondition = new SysCondition(new Condition("Condition"), currentThread->space);
	// Add it to the CV array
	int index = conditionTable->Put(newCondition);
	DEBUG('b',"Creating Condition, at index %d.\n", index);
	return index;
}

void DestroyCondition_Syscall(int index) {
	// if the CV is in use
		// set "to be destroyed flag" on the CV
		// ((SysCondition) conditionTable->Get(index))->flaggedForDelete = true;
	// else
		// Delete this CV and null the pointer in the array
	DEBUG('b',"Destroying Condition, at index %d.\n", index);
		conditionTable->Remove(index);
}

void ExceptionHandler(ExceptionType which) {
    int type = machine->ReadRegister(2); // Which syscall?
    int rv=0; 	// the return value from a syscall

    if ( which == SyscallException ) {
		switch (type) {
			default:
				DEBUG('a', "Unknown syscall - shutting down.\n");
			case SC_Halt:
				DEBUG('a', "Shutdown, initiated by user program.\n");
				interrupt->Halt();
				break;
			case SC_Create:
				DEBUG('a', "Create syscall.\n");
				Create_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
				break;
			case SC_Open:
				DEBUG('a', "Open syscall.\n");
				rv = Open_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
				break;
			case SC_Write:
				DEBUG('a', "Write syscall.\n");
				Write_Syscall(machine->ReadRegister(4),
					  machine->ReadRegister(5),
					  machine->ReadRegister(6));
				break;
			case SC_Read:
				DEBUG('a', "Read syscall.\n");
				rv = Read_Syscall(machine->ReadRegister(4),
					  machine->ReadRegister(5),
					  machine->ReadRegister(6));
				break;
			case SC_Close:
				DEBUG('a', "Close syscall.\n");
				Close_Syscall(machine->ReadRegister(4));
				break;
			case SC_Exec:
				DEBUG('a', "Exec syscall.\n");
				rv = Exec_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
				//rv = Exec_Syscall(""); // TODO change this
				break;
			case SC_Exit:
				DEBUG('a', "Exit syscall.\n");
				Exit_Syscall(machine->ReadRegister(4));
				break;
			case SC_Fork:
				DEBUG('a', "Fork syscall.\n");
				Fork_Syscall(machine->ReadRegister(4));
				break;
			case SC_Yield:
				DEBUG('a', "Yield syscall.\n");
				Yield_Syscall();
				break;
			case SC_Acquire:
				DEBUG('a', "Acquire syscall.\n");
				Acquire_Syscall(machine->ReadRegister(4));
				break;
			case SC_Release:
				DEBUG('a', "Release syscall.\n");
				Release_Syscall(machine->ReadRegister(4));
				break;
			case SC_Wait:
				DEBUG('a', "Wait syscall.\n");
				Wait_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
				break;
			case SC_Signal:
				DEBUG('a', "Signal syscall.\n");
				Signal_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
				break;
			case SC_Broadcast:
				DEBUG('a', "Broadcast syscall.\n");
				Broadcast_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
				break;
			case SC_CreateLock:
				DEBUG('a', "CreateLock syscall.\n");
				rv = CreateLock_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
				break;
			case SC_DestroyLock:
				DEBUG('a', "DestroyLock syscall.\n");
				DestroyLock_Syscall(machine->ReadRegister(4));
				break;
			case SC_CreateCondition:
				DEBUG('a', "CreateCondition syscall.\n");
				rv = CreateCondition_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
				break;
			case SC_DestroyCondition:
				DEBUG('a', "DestroyCondition syscall.\n");
				DestroyCondition_Syscall(machine->ReadRegister(4));
				break;
		}

		// Put in the return value and increment the PC
		machine->WriteRegister(2,rv);
		machine->WriteRegister(PrevPCReg,machine->ReadRegister(PCReg));
		machine->WriteRegister(PCReg,machine->ReadRegister(NextPCReg));
		machine->WriteRegister(NextPCReg,machine->ReadRegister(PCReg)+4);
		return;
    } 
	else if (which == PageFaultException){
		PageCallExceptionHandler(machine->ReadRegister(BadVAddrReg));
	}
	else {
      cout<<"Unexpected user mode exception - which:"<<which<<"  type:"<< type<<endl;
      interrupt->Halt();
    }
}
